package pool

import (
	"errors"
	"math/rand"
	"sync"
	"time"
)

type ThreadPool struct {
	minThread, maxThread int
	threadNumber         int
	threadCreateMutex    sync.Mutex
	channelLength        int

	createTimeout time.Duration
	callTimeout   time.Duration

	tChannel     chan chan Task
	taskFunction func(task Task) Result
}

const (
	defaultMinThread     = 10
	defaultMaxThread     = 100
	defaultCreateTimeout = 10 * time.Second
	defaultCallTimeout   = 200 * time.Millisecond * 4
	//defaultChannelLength = 1000
)

func NewThreadPool(minThread, maxThread, channelLength int, taskFunction func(task Task) Result) *ThreadPool {
	threadPool := ThreadPool{
		minThread: defaultMinThread,
		maxThread: defaultMaxThread,
		//channelLength: defaultChannelLength,
		createTimeout: defaultCreateTimeout,
		callTimeout:   defaultCallTimeout,
		taskFunction:  taskFunction,
	}
	if minThread != 0 {
		threadPool.minThread = minThread
	}
	if maxThread != 0 {
		threadPool.maxThread = maxThread
	}
	if channelLength != 0 {
		threadPool.channelLength = channelLength
	}
	return &threadPool
}

func (t *ThreadPool) createThread() error {
	t.threadCreateMutex.Lock()
	if t.threadNumber >= t.maxThread {
		t.threadCreateMutex.Unlock()
		return errors.New("out length")
	}
	t.threadNumber++
	t.threadCreateMutex.Unlock()
	taskChan := make(chan Task)
	timer := time.NewTimer(t.createTimeout)
	go func() {
		for {
			select {
			case t.tChannel <- taskChan:
				task := <-taskChan
				task.Result <- t.taskFunction(task)
				close(task.Result)
				timer.Reset(t.createTimeout)
			case <-timer.C:
				if t.threadNumber > t.minThread {
					t.threadCreateMutex.Lock()
					if t.threadNumber > t.minThread {
						t.threadNumber--
					}
					t.threadCreateMutex.Unlock()
					close(taskChan)
				}
				return
			}

		}
	}()
	return nil
}

func (t *ThreadPool) Run() {
	t.tChannel = make(chan chan Task)
	for i := 0; i < t.minThread; i++ {
		_ = t.createThread()
	}
}

func (t *ThreadPool) Call(value Value) Result {
	timer := time.NewTimer(t.callTimeout + time.Duration(rand.Int31n(2))*time.Millisecond*200)
	for i := 0; i < 3; i++ {
		select {
		case taskChan := <-t.tChannel:
			task := Task{
				Value:  value,
				Result: make(chan Result),
			}

			taskChan <- task
			return <-task.Result
		case <-timer.C:
			if t.threadNumber > t.maxThread {
				err := t.createThread()
				return Result{
					Error: err,
				}
			}
		}
		timer.Reset(t.callTimeout)
	}

	return Result{
		Error: errors.New("call time out"),
	}
}
